package yolo

import (
	"appengine"
	"appengine/memcache"
	"encoding/json"
)

type TryConflictError struct {
	message string
}

func (err TryConflictError) Error() string {
	return err.message
}

var TryConflictErr = TryConflictError{message: "TryConflict error!"}

type CounterData struct {
	Count int
}

type Counter struct {
	Name string
}

func (counter *Counter) ReadOrError(c appengine.Context) (int, error) {
	var data CounterData
	_, err := memcache.JSON.Get(c, counter.Name, &data)
	return data.Count, err
}

func (counter *Counter) Read(c appengine.Context) (int, error) {
	var data CounterData
	ensureExist(c, counter.Name, &data)
	_, err := memcache.JSON.Get(c, counter.Name, &data)
	return data.Count, err
}

func (counter *Counter) Write(c appengine.Context, v int) error {
	data := &CounterData{v}
	return memcache.JSON.Set(c, &memcache.Item{Key: counter.Name, Object: data})
}

func (counter *Counter) Delete(c appengine.Context) error {
	return memcache.Delete(c, counter.Name)
}

func (counter *Counter) Increment(c appengine.Context) (int, error) {
	data := &CounterData{}
	err := updateMemcacheItem(c, counter.Name, data, func(v interface{}) error {
		data = v.(*CounterData)
		data.Count += 1
		return nil
	})
	return data.Count, err
}

func (counter *Counter) Decrement(c appengine.Context) (int, error) {
	data := &CounterData{}
	err := updateMemcacheItem(c, counter.Name, data, func(v interface{}) error {
		data = v.(*CounterData)
		data.Count -= 1
		if data.Count < 0 {
			data.Count = 0
		}
		return nil
	})
	return data.Count, err
}

func (counter *Counter) TryIncrement(c appengine.Context, expect int) error {
	data := &CounterData{}
	err := updateMemcacheItem(c, counter.Name, data, func(v interface{}) error {
		data = v.(*CounterData)
		if data.Count != expect {
			return TryConflictErr
		}
		data.Count += 1
		return nil
	})
	if err == TryConflictErr {
		return nil
	}
	return err
}

func ensureExist(c appengine.Context, name string, v interface{}) error {
	_, err := memcache.Get(c, name)
	if err == nil {
		return err
	}
	if err == memcache.ErrCacheMiss {
		bs, err := json.Marshal(v)
		if err != nil {
			return err
		}
		err = memcache.Set(c, &memcache.Item{Key: name, Value: bs})
	}
	return err
}

// Atomic
func updateMemcacheItem(c appengine.Context, name string, v interface{}, updater func(v interface{}) error) error {
	for {
		item, err := memcache.Get(c, name)
		if err != nil {
			if err != memcache.ErrCacheMiss {
				return err
			}
			err = updater(v)
			if err != nil {
				return err
			}
			bs, err := json.Marshal(v)
			if err != nil {
				return err
			}
			return memcache.Set(c, &memcache.Item{Key: name, Value: bs})
		}
		if err := json.Unmarshal(item.Value, v); err != nil {
			return err
		}

		err = updater(v)
		if err != nil {
			return err
		}

		bs, err := json.Marshal(v)
		if err != nil {
			return err
		}
		item.Value = bs
		err = memcache.CompareAndSwap(c, item)
		if err == nil {
			break
		}
		if err != nil && err != memcache.ErrCASConflict {
			return err
		}
	}
	return nil
}
